package rs.etf.es.algorithm;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;

import rs.etf.es.algorithm.states.SetDefaultLambdaValuesState;

public class Algorithm implements Runnable, Serializable {

    public static int NUMBER_OF_STATES = 8;

    // states numbers
    public static int SET_DEFAULT_LAMBDA_VALUES_STATE_INDEX = 0;
    public static int SET_DEFAULT_LAMBDA_MESSAGE_STATE_INDEX = 1;
    public static int SET_INITIAL_ROOT_PROBABILITIES_AND_PI_VALUES_STATE_INDEX = 2;
    public static int SEND_PI_MESSAGES_FROM_ROOT_STATE_INDEX = 3;

    // state borders
    public static int UPDATE_PHASE_LAST_STATE_INDEX = 3;

    private Queue<AlgorithmState> nextStates;
    private AlgorithmState currentState;

    public Algorithm() {
	nextStates = new LinkedList<AlgorithmState>();

    }

    @Override
    public void run() {

	SetDefaultLambdaValuesState newState = new SetDefaultLambdaValuesState();
	newState.setAlgorithm(this);
	pushAlgorithmState(newState);
	currentState = newState;
	// this creates initial memento
	try {
	    createMemento(true);
	} catch (IOException e) {
	    e.printStackTrace();
	}

	// algorithmImplementation();
	while (true) {
	    AlgorithmBlocker.instance().stopHere();
	    try {
		int decision = AlgorithmBlocker.instance().getSelectedOperation();
		if (decision == Constants.GO_NEXT) {
		    nextStep();
		} else if (decision == Constants.GO_PREVIOUS) {
		    takeLastMemento();
		} else if (decision == Constants.GO_RESTART) {
		    restartAlgorithm();
		}
	    } catch (InterruptedException e) {
		e.printStackTrace();
	    } catch (IOException e) {
		e.printStackTrace();
	    } catch (ClassNotFoundException e) {
		e.printStackTrace();
	    }
	}

    }

    public void algorithmImplementation() {
    };

    public void nextStep() throws InterruptedException, IOException {
	currentState = nextStates.poll();
	if (currentState == null) {
	    return;
	}
	currentState.performAction();

	if (currentState != null) {
	    createMemento(false);
	}

    }

    private void createMemento(boolean isInitialMemento) throws IOException {
	// serialize NodeAddressBook
	ByteArrayOutputStream nodeAddressBookData = new ByteArrayOutputStream();
	ObjectOutputStream nodeAddressBookStream = new ObjectOutputStream(nodeAddressBookData);
	nodeAddressBookStream.writeObject(NodeAddressBook.instance().getAddressBook());

	// serialize nextStates
	ByteArrayOutputStream states = new ByteArrayOutputStream();
	ObjectOutputStream statesStream = new ObjectOutputStream(states);
	statesStream.writeObject(nextStates);

	// serialize request manager
	ByteArrayOutputStream requests = new ByteArrayOutputStream();
	ObjectOutputStream requestsStream = new ObjectOutputStream(requests);
	requestsStream.writeObject(RequestManager.instance());

	// clone state
	AlgorithmState oldState = (AlgorithmState) currentState.clone();
	oldState.refreshNodes();

	// clone log text
	String oldText = JTextAreaLogger.instance().getCurrentText();

	AlgorithmMemento memento = new AlgorithmMemento();
	memento.setNextAlgorithmSteps(states.toByteArray());
	memento.setLogText(oldText);
	memento.setCurrentState(oldState);
	memento.setRootName(NodeAddressBook.instance().getRoot().getName());
	memento.setNodeAddressBookArray(nodeAddressBookData.toByteArray());
	memento.setRequestManager(requests.toByteArray());

	if (isInitialMemento) {
	    MementoCaretaker.instance().setInitialMemento(memento);
	} else {
	    MementoCaretaker.instance().pushCurrentMemento(memento);
	}

    }

    @SuppressWarnings("unchecked")
    private void takeLastMemento() throws IOException, ClassNotFoundException {
	AlgorithmMemento memento = MementoCaretaker.instance().oneStepBack();
	if (memento == null) {
	    memento = MementoCaretaker.instance().getInitialMemento();
	}
	// read nodes
	ByteArrayInputStream bis = new ByteArrayInputStream(memento.getNodeAddressBookArray());
	ObjectInputStream ois = new ObjectInputStream(bis);
	HashMap<String, Node> addressBook = (HashMap<String, Node>) ois.readObject();

	// read next states
	ByteArrayInputStream bisStates = new ByteArrayInputStream(memento.getNextAlgorithmSteps());
	ObjectInputStream oisStates = new ObjectInputStream(bisStates);
	nextStates = (LinkedList<AlgorithmState>) oisStates.readObject();

	// read request manager
	ByteArrayInputStream bisReq = new ByteArrayInputStream(memento.getRequestManager());
	ObjectInputStream bisReqStream = new ObjectInputStream(bisReq);
	RequestManager.requestManager = (RequestManager) bisReqStream.readObject();
	RequestManager.instance().setAlgorithm(this);

	NodeAddressBook.instance().setAddressBook(addressBook);
	NodeAddressBook.instance().markAsRoot(NodeAddressBook.instance().getNode(memento.getRootName()));

	currentState = memento.getCurrentState();
	currentState.refreshNodes();
	JTextAreaLogger.instance().setCurrentText(memento.getLogText());

	createMemento(false);
    }

    public void restartAlgorithm() throws ClassNotFoundException, IOException {
	MementoCaretaker.instance().emptyStack();
	takeLastMemento();
    }

    public void pushAlgorithmState(AlgorithmState newState) {
	nextStates.offer(newState);
    }

}
